unit HotkeyHandler;

interface

uses windows,classes,SyncObjs,messages,Luan1;

type thotkeyitem=record
  keys: TKeyCombo;
  windowtonotify: thandle;
  id: integer;
  //extra
  fuModifiers: word;
  uVirtKey:word;
  handler2:boolean;
end;

type Thotkeythread=class(tthread)
  private
  public
    suspended: boolean;
    hotkeylist: array of thotkeyitem;
    procedure execute; override;
end;

function RegisterHotKey(hWnd: HWND; id: Integer; fsModifiers, vk: UINT): BOOL; stdcall;
function RegisterHotKey2(hWnd: HWND; id: Integer; keys: TKeyCombo): boolean;
function UnregisterHotKey(hWnd: HWND; id: Integer): BOOL; stdcall;
function OldUnregisterHotKey(hWnd: HWND; id: Integer): BOOL; stdcall;
function CheckKeyCombo(keycombo: tkeycombo):boolean;
procedure ClearHotkeylist;
procedure SuspendHotkeyHandler;
procedure ResumeHotkeyHandler;


var hotkeythread: THotkeythread;
    CSKeys: TCriticalSection;

implementation

procedure SuspendHotkeyHandler;
begin
  if not hotkeythread.suspended then cskeys.Enter;
  hotkeythread.suspended:=true;
end;

procedure ResumeHotkeyHandler;
begin
  if hotkeythread.suspended then cskeys.Leave;
  hotkeythread.suspended:=false;
end;

procedure ClearHotkeylist;
var i:integer;
begin
  CSKeys.Enter;
  //it's now safe to clear the list
  setlength(hotkeythread.hotkeylist,0);

  CSKeys.Leave;
end;

function CheckKeyCombo(keycombo: tkeycombo):boolean;
var i: integer;
begin
  result:=false;

  if keycombo[0]<>0 then
  begin
    result:=true;

    for i:=0 to length(keycombo) do
    begin
      if (keycombo[i]=0) then exit;
      if (getasynckeystate(keycombo[i])=0) then
      begin
        result:=false;
        exit;
      end;
    end;

  end;
end;

function RegisterHotKey2(hWnd: HWND; id: Integer; keys: TKeyCombo): boolean;
begin
  CSKeys.Enter;
  try
    checkkeycombo(keys);

    setlength(hotkeythread.hotkeylist,length(hotkeythread.hotkeylist)+1);
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].windowtonotify:=hWnd;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].keys:=keys;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].id:=id;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].handler2:=true;

    result:=true;
  finally
    CSKeys.Leave;
  end;
end;


function RegisterHotKey(hWnd: HWND; id: Integer; fsModifiers, vk: UINT): BOOL; stdcall;
var i: integer;
begin
  CSKeys.Enter;
  try
    setlength(hotkeythread.hotkeylist,length(hotkeythread.hotkeylist)+1);
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].windowtonotify:=hWnd;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].id:=id;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].fuModifiers:=fsmodifiers;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].uVirtKey:=vk;
    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].handler2:=false;

    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].keys[0]:=VK;
    i:=1;
    
    if (fsModifiers and MOD_CONTROL)>0 then
    begin
      hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].keys[i]:=vk_control;
      inc(i);
    end;

    if (fsModifiers and MOD_ALT)>0 then
    begin
      hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].keys[i]:=vk_menu;
      inc(i);
    end;

    if (fsModifiers and MOD_Shift)>0 then
    begin
      hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].keys[i]:=vk_shift;
      inc(i);
    end;

    hotkeythread.hotkeylist[length(hotkeythread.hotkeylist)-1].keys[i]:=0;

    result:=true;
  finally
    CSKeys.Leave;
  end;
end;

function OldUnregisterHotKey(hWnd: HWND; id: Integer): BOOL; stdcall;
begin
  result:=windows.UnregisterHotKey(hWnd,id);
end;

function UnregisterHotKey(hWnd: HWND; id: Integer): BOOL; stdcall;
var i,j: integer;
    found: boolean;
begin
  result:=false;
  found:=false;
  
  CSKeys.Enter;
  try
    for i:=0 to length(hotkeythread.hotkeylist)-1 do
    begin
      if (hotkeythread.hotkeylist[i].windowtonotify=hwnd) and
         (hotkeythread.hotkeylist[i].id=id) then
      begin
        found:=true;
        for j:=i to length(hotkeythread.hotkeylist)-2 do
          hotkeythread.hotkeylist[j]:=hotkeythread.hotkeylist[j+1];

        break;
      end;
    end;

    if not found then
      result:=windows.UnregisterHotKey(hWnd,id)
    else
    begin
      setlength(hotkeythread.hotkeylist,length(hotkeythread.hotkeylist)-1);
      result:=true;
    end;
  finally
    CSKeys.Leave;
  end;
end;

procedure THotkeyThread.execute;
var i: integer;
    a,b,c: dword;
begin
  while not terminated do
  begin
    try
      CSKeys.Enter;
      try
        for i:=0 to length(hotkeylist)-1 do
          if checkkeycombo(hotkeylist[i].keys) then
          begin
            //the hotkey got pressed
            a:=hotkeylist[i].windowtonotify;
            b:=hotkeylist[i].id;
            c:=(hotkeylist[i].uVirtKey shl 16)+hotkeylist[i].fuModifiers;
            CSKeys.Leave;

            if hotkeylist[i].handler2 then
              sendmessage(a,WM_USER+800,b,0) //
            else
              sendmessage(a,WM_HOTKEY,b,c);

            sleep(250);
            checkkeycombo(hotkeylist[i].keys);

            CSKeys.enter;
          end;
      finally
        CSKeys.Leave;
      end;
      
    except
    end;

    sleep(100);
  end;
end;

initialization
  CSKeys:=TCriticalSection.Create;
  hotkeythread:=Thotkeythread.Create(true);
  hotkeythread.Resume;

finalization
  hotkeythread.Terminate;
  CSKeys.Free;
end.
